package com.company.myfirstjmixproject.screen.user;

import com.company.myfirstjmixproject.entity.OnboardingStatus;
import com.company.myfirstjmixproject.entity.Step;
import com.company.myfirstjmixproject.entity.User;
import com.company.myfirstjmixproject.entity.UserStep;
import io.jmix.core.DataManager;
import io.jmix.core.EntityStates;
import io.jmix.core.security.event.SingleUserPasswordChangeEvent;
import io.jmix.ui.Notifications;
import io.jmix.ui.UiComponents;
import io.jmix.ui.component.*;
import io.jmix.ui.model.CollectionContainer;
import io.jmix.ui.model.CollectionPropertyContainer;
import io.jmix.ui.model.DataContext;
import io.jmix.ui.model.InstanceContainer;
import io.jmix.ui.navigation.Route;
import io.jmix.ui.screen.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.TimeZone;

@UiController("User.edit")
@UiDescriptor("user-edit.xml")
@EditedEntityContainer("userDc")
@Route(value = "users/edit", parentPrefix = "users")
public class UserEdit extends StandardEditor<User> {

    @Autowired
    private UiComponents uiComponents;

    @Autowired
    private DataManager dataManager;

    @Autowired
    private DataContext dataContext;

    @Autowired
    private CollectionPropertyContainer<UserStep> stepsDc;

    @Autowired
    private EntityStates entityStates;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private PasswordField passwordField;

    @Autowired
    private TextField<String> usernameField;

    @Autowired
    private PasswordField confirmPasswordField;

    @Autowired
    private Notifications notifications;

    @Autowired
    private MessageBundle messageBundle;

    @Autowired
    private ComboBox<String> timeZoneField;

    private boolean isNewEntity;

    @Subscribe
    public void onInit(InitEvent event) {
        timeZoneField.setOptionsList(Arrays.asList(TimeZone.getAvailableIDs()));
    }

    @Subscribe
    public void onInitEntity(InitEntityEvent<User> event) {
        usernameField.setEditable(true);
        passwordField.setVisible(true);
        confirmPasswordField.setVisible(true);
        isNewEntity = true;

        User user = event.getEntity();
        user.setOnboardingStatus(OnboardingStatus.NOT_STARTED);
    }

    @Subscribe
    public void onInitEntity1(InitEntityEvent<User> event) {
        // 我自己创建的处理器方法，看看有两个处理方法的时候，是不是都会执行
        // System.err.println("Custom initEntityEvent method executed ...");
    }

    @Subscribe
    public void onAfterShow(AfterShowEvent event) {
        if (entityStates.isNew(getEditedEntity())) {
            usernameField.focus();
        }
    }

    @Subscribe
    protected void onBeforeCommit(BeforeCommitChangesEvent event) {
        if (entityStates.isNew(getEditedEntity())) {
            if (!Objects.equals(passwordField.getValue(), confirmPasswordField.getValue())) {
                notifications.create(Notifications.NotificationType.WARNING).withCaption(messageBundle.getMessage("passwordsDoNotMatch")).show();
                event.preventCommit();
            }
            getEditedEntity().setPassword(passwordEncoder.encode(passwordField.getValue()));
        }
    }

    @Subscribe(target = Target.DATA_CONTEXT)
    public void onPostCommit(DataContext.PostCommitEvent event) {
        if (isNewEntity) {
            getApplicationContext().publishEvent(new SingleUserPasswordChangeEvent(getEditedEntity().getUsername(), passwordField.getValue()));
        }
    }

    /**
     * this is a first button of my custom generate
     *
     * @param event 按钮🔘点击事件
     */
    @Subscribe("generateButton")
    public void onGenerateButtonClick(Button.ClickEvent event) {

        // 获取正在编辑的User实体
        User user = getEditedEntity();

        if (Objects.isNull(user.getJoiningDate())) {
            // notifications从字面意思理解，这是一个用来做提示信息的组件
            notifications.create().withCaption("Cannot generate steps for user without 'joining date'").show();
            return;
        }

        // 加载已经添加的入职步骤
        List<Step> steps = dataManager.load(Step.class).query("select s from Step s order by s.sortValue asc").list();

        // 过滤stepDc集合容器中已经存在的步骤
        for (Step step : steps) {
            if (stepsDc.getItems().stream().noneMatch(userStep -> userStep.getStep().equals(step))) {
                UserStep userStep = dataContext.create(UserStep.class);
                userStep.setUser(user);
                userStep.setStep(step);
                userStep.setDueDate(user.getJoiningDate().plusDays(step.getDuration()));
                userStep.setSortValue(step.getSortValue());
                stepsDc.getMutableItems().add(userStep);
            }
        }

    }

    /**
     * <p>
     * Install注解表示该方法是一个代理（delegate）：一个UI组件（这个case是一个表格）会在生命周期的某个阶段调用该方法
     * <p/>
     *
     * @param userStep 这个特殊的代理（列生成器）接收一个实体实例作为参数，该实例在表格中的一行显示
     * @return Component 组件 这个case返回一个checkBox复选框☑️
     */
    @Install(to = "stepsTable.completed", subject = "columnGenerator")
    private Component stepsTableCompletedColumnGenerator(UserStep userStep) {

        // 通过UiComponent工厂生产CheckBox组件
        CheckBox checkBox = uiComponents.create(CheckBox.class);
        checkBox.setValue(userStep.getCompletedDate() != null);

        // 给复选框注册值改变监听器
        checkBox.addValueChangeListener(e -> {
            // 当点击复选框时，它的值会发生变化，复选框会调用其ValueChangeEvent监听器，在监听器中，设置UserStep实体的completeDate属性
            if (userStep.getCompletedDate() == null) {
                userStep.setCompletedDate(LocalDate.now());
            } else {
                userStep.setCompletedDate(null);
            }
        });

        return checkBox;
    }

    /**
     * 处理器实体属性发生变化的时候会调用该方法
     *
     * @param event ItemPropertyChangeEvent 实体属性更改事件
     */
    @Subscribe(id = "stepsDc", target = Target.DATA_CONTAINER)
    public void onStepsDcItemPropertyChange(InstanceContainer.ItemPropertyChangeEvent<UserStep> event) {
        updateOnBoardingStatus();
    }

    /**
     * 数据容器中数据发生改变（添加/删除）会调用该方法
     *
     * @param event CollectionChangeEvent 容器数据更改事件
     */
    @Subscribe(id = "stepsDc", target = Target.DATA_CONTAINER)
    public void onStepsDcCollectionChange(CollectionContainer.CollectionChangeEvent<UserStep> event) {
        updateOnBoardingStatus();
    }

    private void updateOnBoardingStatus() {

        User user = getEditedEntity();

        long completedCount = user.getSteps() == null ? 0 : user.getSteps().stream().filter(us -> us.getCompletedDate() != null).count();
        if (completedCount == 0) {
            user.setOnboardingStatus(OnboardingStatus.NOT_STARTED);
        } else if (completedCount == user.getSteps().size()) {
            user.setOnboardingStatus(OnboardingStatus.COMPLETED);
        } else {
            user.setOnboardingStatus(OnboardingStatus.IN_PROGRESS);
        }
    }
}